using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif

public class Inventory : MonoBehaviour, ISavable
{
    public static Inventory Instance { get; private set; }
    private Player player;
    public List<ItemData> startingItems;
    public List<InventoryItem> Equipment { get; private set; }
    public Dictionary<ItemDataEquipment, InventoryItem> EquipmentDictionary { get; private set; }
    public List<InventoryItem> InventoryItems { get; private set; }
    public Dictionary<ItemData, InventoryItem> InventoryDictionary { get; private set; }
    public List<InventoryItem> Stash { get; private set; }
    public Dictionary<ItemData, InventoryItem> StashDictionary { get; private set; }
    private Ui ui;
    private UiItemSlot[] inventoryItemSlots;
    private UiItemSlot[] stashItemSlots;
    private UiEquipmentSlot[] equipmentItemSlots;
    private UiStatSlot[] statSlots;
    [Header("Items cooldown")]
    private float lastFlaskUseTime = Mathf.NegativeInfinity;
    private float lastTimeUsedArmor = Mathf.NegativeInfinity;
    public float FlaskCooldown { get; private set; }
    public float ArmorCooldown { get; private set; }
    // [Header("Data base")]
    public ItemData[] ItemDatas;
    public List<InventoryItem> loadedItems;
    public List<ItemDataEquipment> loadedEquipments;

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
        Equipment = new();
        EquipmentDictionary = new();
        InventoryItems = new();
        InventoryDictionary = new();
        Stash = new();
        StashDictionary = new();
        ui = FindAnyObjectByType<Ui>();
        player = FindAnyObjectByType<Player>();
        inventoryItemSlots = ui.InventoryUi.GetComponentsInChildren<UiItemSlot>();
        stashItemSlots = ui.StashUi.GetComponentsInChildren<UiItemSlot>();
        equipmentItemSlots = ui.EquipmentUi.GetComponentsInChildren<UiEquipmentSlot>();
        ItemDatas = Resources.LoadAll<ItemData>("Data/Items");
    }

    private void Start()
    {
        statSlots = ui.StatUi.GetComponentsInChildren<UiStatSlot>();
        AddStartingItems();
    }

    private void AddStartingItems()
    {
        if (loadedEquipments.Count > 0)
        {
            foreach (ItemDataEquipment item in loadedEquipments)
            {
                EquipItem(item);
            }
        }
        if (loadedItems.Count > 0)
        {
            foreach (InventoryItem item in loadedItems)
            {
                for (int i = 0; i < item.StackSize; i++)
                {
                    AddItem(item.data);
                }
            }
            return;
        }
        foreach (ItemData data in startingItems.Where(data => data))
        {
            if (data == null) continue;
            AddItem(data);
        }
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.L))
        {
            if (InventoryItems.Count == 0) return;
            ItemData newItem = InventoryItems[^1].data;
            RemoveItem(newItem);
        }
    }

    public void EquipItem(ItemData item)
    {
        ItemDataEquipment newEquipmentItem = item as ItemDataEquipment;
        InventoryItem newItem = new(newEquipmentItem);
        ItemDataEquipment oldEquipment = null;
        foreach (var kvp in EquipmentDictionary.Where(kvp =>
                     newEquipmentItem != null && kvp.Key.equipmentType == newEquipmentItem.equipmentType))
        {
            oldEquipment = kvp.Key;
        }
        if (oldEquipment)
        {
            UnequipItem(oldEquipment);
            AddItem(oldEquipment);
        }
        Equipment.Add(newItem);
        if (newEquipmentItem)
        {
            EquipmentDictionary.Add(newEquipmentItem, newItem);
            newEquipmentItem.AddModifiers();
        }
        RemoveItem(item);
        UpdateSlotUi();
    }

    public void UnequipItem(ItemDataEquipment itemToRemove)
    {
        if (EquipmentDictionary.Count == 0) return;
        if (!EquipmentDictionary.TryGetValue(itemToRemove, out InventoryItem value)) return;
        _ = Equipment.Remove(value);
        _ = EquipmentDictionary.Remove(itemToRemove);
        itemToRemove.RemoveModifiers();
    }

    private void UpdateSlotUi()
    {
        foreach (var uiEquipmentSlot in equipmentItemSlots)
        {
            foreach (var kvp in EquipmentDictionary.Where(kvp => kvp.Key.equipmentType == uiEquipmentSlot.SlotType))
            {
                uiEquipmentSlot.UpdateSlot(kvp.Value);
            }
        }
        foreach (var uiItemSlot in inventoryItemSlots)
        {
            uiItemSlot.ClearSlot();
        }
        foreach (var uiItemSlot in stashItemSlots)
        {
            uiItemSlot.ClearSlot();
        }
        for (int i = 0; i < InventoryItems.Count; i++)
        {
            inventoryItemSlots[i].UpdateSlot(InventoryItems[i]);
        }
        for (int i = 0; i < Stash.Count; i++)
        {
            stashItemSlots[i].UpdateSlot(Stash[i]);
        }
        UpdateStatsUi();
    }

    public void UpdateStatsUi()
    {
        foreach (var uiStatSlot in statSlots)
        {
            uiStatSlot.UpdateStatValueUi();
        }
    }

    public void AddItem(ItemData item)
    {
        switch (item.ItemType)
        {
            case ItemType.Equipment when CanAddItem():
                AddToInventory(item);
                break;
            case ItemType.Material:
                AddToStash(item);
                break;
            case ItemType.None:
            case ItemType.Consumable:
            case ItemType.QuestItem:
            default:
                throw new ArgumentOutOfRangeException();
        }
        UpdateSlotUi();
    }

    private void AddToStash(ItemData item)
    {
        if (StashDictionary.TryGetValue(item, out InventoryItem value))
        {
            value.AddStack();
        }
        else
        {
            InventoryItem newItem = new(item);
            Stash.Add(newItem);
            StashDictionary.Add(item, newItem);
        }
    }

    private void AddToInventory(ItemData item)
    {
        if (InventoryDictionary.TryGetValue(item, out InventoryItem value))
        {
            value.AddStack();
        }
        else
        {
            InventoryItem newItem = new(item);
            InventoryItems.Add(newItem);
            InventoryDictionary.Add(item, newItem);
        }
    }

    public void RemoveItem(ItemData item)
    {
        if (InventoryDictionary.TryGetValue(item, out InventoryItem inventoryValue))
        {
            if (inventoryValue.StackSize <= 1)
            {
                _ = InventoryItems.Remove(inventoryValue);
                _ = InventoryDictionary.Remove(item);
            }
            else
            {
                inventoryValue.RemoveStack();
            }
        }
        if (StashDictionary.TryGetValue(item, out InventoryItem stashValue))
        {
            if (stashValue.StackSize <= 1)
            {
                _ = Stash.Remove(stashValue);
                _ = StashDictionary.Remove(item);
            }
            else
            {
                stashValue.RemoveStack();
            }
        }
        UpdateSlotUi();
    }

    public bool CanAddItem() => InventoryItems.Count < inventoryItemSlots.Length;

    public List<InventoryItem> GetEquipmentList() => Equipment;

    public List<InventoryItem> GetStashList() => Stash;

    public ItemDataEquipment GetEquipment(EquipmentType type)
    {
        ItemDataEquipment equipmentItem = null;
        foreach (var kv in EquipmentDictionary.Where(kv => kv.Key.equipmentType == type))
        {
            equipmentItem = kv.Key;
        }
        return equipmentItem;
    }

    public void UseFlask()
    {
        ItemDataEquipment currentFlask = GetEquipment(EquipmentType.Flask);
        if (!currentFlask)
            return;
        bool canUseFlask = Time.time >= lastFlaskUseTime + currentFlask.itemCooldown;
        if (!canUseFlask)
            return;
        currentFlask.Effect(null, player);
        lastFlaskUseTime = Time.time;
    }

    public bool CanUseArmor()
    {
        ItemDataEquipment currentArmor = GetEquipment(EquipmentType.Armor);
        if (currentArmor == null)
        {
            return false;
        }
        if (!(Time.time >= lastTimeUsedArmor + currentArmor.itemCooldown)) return false;
        lastTimeUsedArmor = Time.time;
        return true;
    }

    public bool CanCraft(ItemDataEquipment craftData, List<InventoryItem> requireMaterials)
    {
        List<InventoryItem> materialsToRemove = new();
        foreach (var material in requireMaterials)
        {
            if (StashDictionary.TryGetValue(material.data, out InventoryItem stashItem))
            {
                if (stashItem.StackSize < material.StackSize)
                {
                    return false;
                }
                else
                {
                    materialsToRemove.Add(stashItem);
                }
            }
            else
            {
                return false;
            }
        }
        foreach (var item in materialsToRemove)
        {
            RemoveItem(item.data);
        }

        AddItem(craftData);
        return true;
    }

    public void Load(GameData data)
    {
        foreach (KeyValuePair<string, int> pair in data.inventory)
        {
            foreach (ItemData item in ItemDatas)
            {
                if (item != null && item.itemId == pair.Key)
                {
                    InventoryItem itemToLoad = new(item);
                    itemToLoad.StackSize = pair.Value;
                    loadedItems.Add(itemToLoad);
                }
            }
        }
        foreach (string loadedItemId in data.equipmentsId)
        {
            foreach (ItemData item in ItemDatas)
            {
                if (item != null && loadedItemId == item.itemId)
                {
                    loadedEquipments.Add(item as ItemDataEquipment);
                }
            }
        }
    }

    public void Save(ref GameData data)
    {
        data.inventory.Clear();
        data.equipmentsId.Clear();
        foreach (KeyValuePair<ItemData, InventoryItem> pair in InventoryDictionary)
        {
            data.inventory.Add(pair.Key.itemId, pair.Value.StackSize);
        }
        foreach (KeyValuePair<ItemData, InventoryItem> pair in StashDictionary)
        {
            data.inventory.Add(pair.Key.itemId, pair.Value.StackSize);
        }
        foreach (KeyValuePair<ItemDataEquipment, InventoryItem> pair in EquipmentDictionary)
        {
            data.equipmentsId.Add(pair.Key.itemId);
        }
    }

#if UNITY_EDITOR
    [ContextMenu("Fill up item data base")]
    private void FillUpItemDataBase() => ItemDatas = GetItemDataBase().ToArray<ItemData>();
    private List<ItemData> GetItemDataBase()
    {
        List<ItemData> itemDataBase = new();
        string[] assetNames = AssetDatabase.FindAssets("", new[] { "Assets/Resources/Data/Items" });
        foreach (string assetName in assetNames)
        {
            string soPath = AssetDatabase.GUIDToAssetPath(assetName);
            ItemData itemData = AssetDatabase.LoadAssetAtPath<ItemData>(soPath);
            itemDataBase.Add(itemData);
        }
        return itemDataBase;
    }
    // private ItemData[] GetItemDataBase(bool isTrue = default)
    // {
    //     string[] assetNames = AssetDatabase.FindAssets("", new[] { "Assets/Resources/Data/Items" });
    //     ItemData[] itemDataBase = new ItemData[assetNames.Length];
    //     for (int i = 0; i < assetNames.Length; i++)
    //     {
    //         string soPath = AssetDatabase.GUIDToAssetPath(assetNames[i]);
    //         ItemData itemData = AssetDatabase.LoadAssetAtPath<ItemData>(soPath);
    //         ItemDatas[i] = itemData;
    //     }
    //     return itemDataBase;
    // }
#endif
}